package com.example.authority.controller;

import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.captcha.generator.RandomGenerator;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.nacos.common.utils.StringUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;

import com.example.commons.entity.User;
import com.example.commons.entity.UserRole;
import com.example.commons.entity.dto.LoginDto;
import com.example.commons.entity.dto.MailBean;
import com.example.commons.entity.dto.RegisterDto;
import com.example.commons.annotations.ClearPerms;
import com.example.commons.annotations.IgnoreParam;
import com.example.commons.annotations.IgoreResult;

import com.example.authority.service.PermissionService;
import com.example.authority.service.UserRoleService;
import com.example.authority.service.UserService;
import com.example.authority.utils.MailUtil;
import com.example.authority.utils.RedisUtils;
import com.example.commons.common.Constant;
import com.example.commons.common.Result;
import com.example.commons.exception.EmailException;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.mail.MessagingException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;

import java.awt.*;
import java.util.HashMap;
import java.util.List;

/**
 * @program: authority-v3.0.7
 * @ClassName:AuthController
 * @description: 认证处理控制器
 * @author:dyy
 * @Version 3.0
 **/

@Slf4j
@RestController
@RequestMapping("/auth")
@Tag(name = "认证授权 前端控制器")
public class AuthController {
    //注入UserService属性
    @Resource
    private UserService userService;

    //注入密码加密PassWordEncoder属性
    @Resource
    private PasswordEncoder passwordEncoder;

    //注入PermissionServcice属性
    @Resource
    private PermissionService permissionService;

    //注入StpInterface属性
    @Resource
    private StpInterface stpInterface;

    // 注入reidisUtils属性
    @Resource
    private RedisUtils redisUtils;

    //注入UserRoleService属性
    @Resource
    private UserRoleService userRoleService;

    //注入MailUtil属性
    @Resource
    private MailUtil mailUtil;


    /**
     * 登录接口
     *
     * @param
     * @return
     */
    @IgnoreParam
    @IgoreResult
    @PostMapping("/login")
    public Result doLogin(@RequestBody LoginDto loginDto) {
        // 邮箱登录逻辑
        if (StringUtils.isNotBlank(loginDto.getEmail())) {
            return handleEmailLogin(loginDto);
        }
        // 用户名登录逻辑
        return handleUsernameLogin(loginDto);
    }


    /**
     * // 使用邮箱
     *
     * @return
     */
    @GetMapping("/email/login/{email}")
    public Result sendLoginEmailCode(@PathVariable String email) throws MessagingException {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getEmail, email);
        User user = userService.getOne(queryWrapper);
        if (user == null) {
            return Result.error("邮箱未注册！");
        }

        String code = RandomUtil.randomNumbers(5);
        MailBean mailBean = new MailBean();
        mailBean.setRecipient(email);
        mailBean.setSubject("登录验证码");
        mailBean.setContent("您的登录验证码为：" + code + "，5分钟内有效");
        mailUtil.sendSimpleMail(mailBean);

        redisUtils.hset(Constant.EMAIL_LOGIN_PREFIX, email, code, 300);
        return Result.success();
    }


    /**
     * // 使用邮箱登录
     *
     * @return
     */
    private Result handleEmailLogin(LoginDto loginDto) {
        // 验证邮箱是否存在
        LambdaQueryWrapper<User> query = new LambdaQueryWrapper<>();
        query.eq(User::getEmail, loginDto.getEmail());
        User user = userService.getOne(query);

        if (user == null) {
            return Result.error("邮箱未注册！");
        }

        // 验证邮箱验证码
        String storedCode = (String) redisUtils.hget(Constant.EMAIL_LOGIN_PREFIX, loginDto.getEmail());
        if (!loginDto.getCode().equals(storedCode)) {
            return Result.error("验证码错误！");
        }

        // 验证用户状态
        if (user.getStatu() == 0L) {
            return Result.error("账户已被封禁！");
        }

        // 执行登录
        return doUserLogin(user);
    }


    /**
     * // 使用账户名登录
     *
     * @return
     */
    private Result handleUsernameLogin(LoginDto loginDto) {
        // 原有用户名登录逻辑
        User user = userService.getOne(new LambdaQueryWrapper<User>()
                .eq(User::getUsername, loginDto.getUsername()));

        // 验证图形验证码
        String code = (String) redisUtils.hget(Constant.CAPTCHA_PREFIX, loginDto.getToken());
        redisUtils.hdel(Constant.CAPTCHA_PREFIX, loginDto.getToken());
        if (!code.equals(loginDto.getCode())) {
            return Result.error("验证码错误！");
        }

        // 验证密码和状态
        if (ObjectUtil.isEmpty(user) || !passwordEncoder.matches(loginDto.getPassword(), user.getPassword())) {
            return Result.error("账户或密码错误");
        }
        if (user.getStatu() == 0L) {
            return Result.error("账户已被封禁！");
        }

        return doUserLogin(user);
    }

    private Result doUserLogin(User user) {
        StpUtil.login(user.getId(), "PC");
        StpUtil.getSession().set("userInfo", user);

        List<String> roleNames = userRoleService.getRoleNameByUserId(user.getId());
        user.setRoleNames(roleNames);

        SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
        return Result.success(MapUtil.builder()
                .put("tokenInfo", tokenInfo)
                .put("roleList", StpUtil.getRoleList())
                .put("userInfo", user)
                .build());
    }


    /**
     * // 查询登录状态，浏览器访问
     *
     * @return
     */
    @GetMapping("/islogin")
    @ClearPerms
    public String isLogin() {
        System.out.println(StpUtil.getLoginId());
        User user = userService.getUserInfo();
        user.setPassword(null);
        log.warn(JSONUtil.toJsonStr(user));
        return "当前会话是否登录：" + StpUtil.isLogin();
    }


    /**
     * // 查询 Token 信息  ---- http://localhost:8081/acc/tokenInfo
     *
     * @return
     */
    @GetMapping("/tokenInfo")
    @ClearPerms(value = true)
    public Result tokenInfo() {
        log.warn(JSONUtil.toJsonStr(StpUtil.getTokenInfo()));
        return Result.success(StpUtil.getTokenInfo());
    }


    /**
     * 获取认证信息
     * userInfo
     * rolePerms
     * PermissionPerms
     * 用于前端显示自己的个人信息
     * v-permmission指令需要的数据
     * //todo role暂时待定使用
     *
     * @return
     */
    @GetMapping("/authInfo")
    public Result getPerms() {
        User userInfo = userService.getUserInfo();
        List<String> roleNames = userRoleService.getRoleNameByUserId(userInfo.getId());
        userInfo.setRoleNames(roleNames);
        userInfo.setPassword(null);
		// 获取用户的权限，角色，路由信息
        return Result.success(
                MapUtil.builder()
                        .put("rolePerms", StpUtil.getRoleList())
                        .put("permissionPerms", StpUtil.getPermissionList())
                        .put("userInfo", userInfo)
                        .put("routers", permissionService.buildTrees(permissionService.getRouters(StpUtil.getLoginId(), "PC")))
                        .build()
        );
    }


    /**
     * 退出登录
     *
     * @return
     */
    @PostMapping("/logout")
    @ClearPerms(value = true)
    public Result logout() {
		// 使用sa-token中的工具类进行退出
        StpUtil.logout();

		// 这个时候就已经退出登录，前端再次发送请求就会报401的状态码
        return Result.success();
    }


    /**
     * 获取验证码
     * 用于登录页面的使用
     *
     * @return
     */
    @GetMapping("/captcha")
    public Result getCaptcha() {
        // 设置key
        String key = UUID.randomUUID().toString();

        //定义图形验证码的长、宽、验证码位数、干扰线数量
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(110, 40, 5, 20);

        //设置背景颜色
        lineCaptcha.setBackground(new Color(255, 255, 255, 255));

        // 设置验证码字符集为仅包含数字
        lineCaptcha.setGenerator(new RandomGenerator("0123456789", 5));

        String base64String = "";
        try {
            base64String = "data:image/png;base64," + lineCaptcha.getImageBase64();

        } catch (Exception e) {
            e.printStackTrace();
        }

        // 放入redis缓存之中
        redisUtils.hset(Constant.CAPTCHA_PREFIX, key, lineCaptcha.getCode(), 120);
        return Result.success(
                MapUtil.builder().put("token", key).put("captchaImg", base64String).build()
        );
    }


    /**
     * 重置密码
     *
     * @param passwordMap
     * @return
     */
    @PostMapping("/repassword")
    @ClearPerms(value = true)
    public Result repassWord(@RequestBody HashMap<String, String> passwordMap) {
       // 获取当前用户
		User user = userService.getUserInfo();

        if (!passwordEncoder.matches(passwordMap.get("oldpass"), user.getPassword())) {
            return Result.error("旧密码输入错误！");
        }
        if (!passwordMap.get("password").equals(passwordMap.get("checkPass"))) {
            return Result.error("两次密码不一致");
        }
        user.setPassword(passwordEncoder.encode(passwordMap.get("password")));
        return Result.success(userService.saveOrUpdate(user));
    }


    /**
     * 注册用户
     * @param registerDto
     * @return
     */
    @PostMapping("/register")
    public Result register(@RequestBody RegisterDto registerDto) {
        // 验证邮箱验证码
        String storedCode = (String) redisUtils.hget(Constant.EMAIL_REGISTER_PREFIX, registerDto.getEmail());
        if (!registerDto.getCode().equals(storedCode)) {
            return Result.error("验证码错误");
        }

        // 校验用户名唯一性
        LambdaQueryWrapper<User> userQuery = new LambdaQueryWrapper<>();
        userQuery.eq(User::getUsername, registerDto.getUsername());
        if (userService.count(userQuery) > 0) {
            return Result.error("用户名已存在");
        }

        User user = new User()
                .setUsername(registerDto.getUsername())
                .setPassword(passwordEncoder.encode(registerDto.getPassword()))
                .setPhone(registerDto.getPhone())
                .setEmail(registerDto.getEmail())
                .setNickname(registerDto.getNickname())
                .setAvatar(registerDto.getAvatar())
                .setStatu(1L);

        userService.save(user);

        UserRole userRole = new UserRole();
        userRole.setUserId(user.getId())
                .setRoleId(Constant.DEFAULT_ROLE);
        userRoleService.save(userRole);

        // 清除验证码缓存
        redisUtils.hdel(Constant.EMAIL_REGISTER_PREFIX, registerDto.getEmail());

        return Result.success();
    }

    @GetMapping("/email/register/{email}")
    public Result sendRegisterEmailCode(@PathVariable String email) throws MessagingException {
        // 检查邮箱是否已注册
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getEmail, email);
        if (userService.count(queryWrapper) > 0) {
            throw new EmailException("该邮箱已被注册！");
        }

        String code = RandomUtil.randomNumbers(5);

        MailBean mailBean = new MailBean();
        mailBean.setRecipient(email);
        mailBean.setSubject("注册验证码");
        mailBean.setContent("您的注册验证码为：" + code + "，5分钟内有效");
        mailUtil.sendSimpleMail(mailBean);

        redisUtils.hset(Constant.EMAIL_REGISTER_PREFIX, email, code, 300);
        return Result.success();
    }


    /**
     * 发送邮箱验证码
     * @param username
     * @return
     * @throws MessagingException
     */
    @GetMapping("/email/{username}")
    public Result getEmailCode(@PathVariable String username) throws MessagingException {
        // 查库
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();

        // 找出用户名是否存在
        queryWrapper.eq(User::getUsername, username);
        User user = userService.getOne(queryWrapper);

        if (user == null) {
            throw new EmailException("账户名错误，请核对重新输入！");
        }
        if ("".equals(user.getEmail())) {
            throw new EmailException("您的邮箱为空，请联系管理员重置密码！");
        }

        // 判断在5分钟内是否已经发送过邮箱验证码，如果发送过就不再重复发送
        if (redisUtils.hget(Constant.EMAIL_PREFIX,user.getUsername())!=null){
            throw new EmailException("验证码已经发送到您的邮箱，请勿重复发送邮箱验证码！");
        }

        // 生成随机5位数字
        String code= RandomUtil.randomNumbers(5);

        // 创建MailBean对象
        MailBean mailBean = new MailBean();
        mailBean.setRecipient(user.getEmail());
        mailBean.setSubject("authority_找回密码");
        mailBean.setContent("重置密码的邮箱验证码为" +code+"，请在5分钟内使用！如非您本人操作，请不要泄漏。"+ DateUtil.now());

        // 通过 MailUtil 进行发送
        mailUtil.sendSimpleMail(mailBean);

        // 将生成的5位数字验证码存储到缓存之中
        redisUtils.hset(Constant.EMAIL_PREFIX,user.getUsername(),code,300);
        return Result.success();
    }


    @PostMapping("/findpassword")
    public Result findPassWord(@RequestBody RegisterDto registerDto){
        // 构造条件
        LambdaQueryWrapper<User> lambdaQueryWrapper=new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(User::getUsername,registerDto.getUsername());

        // 基于username构造条件进行查库
        User user=userService.getOne(lambdaQueryWrapper);

        // 如果为空就抛出异常提示用户，账户名有误
        if (user==null){return Result.error("账户名有误，请重新输入！");}

        // 通过redis缓存判断是否已经发送出验证码
        if (redisUtils.hget(Constant.EMAIL_PREFIX,user.getUsername())==null){
            throw new EmailException("邮箱验证码为空，请点击获取邮箱验证码按钮获取邮箱验证码！");
        }

        // 通过redis缓存判断前端输入的和后端发送的一致，如果不一致，让用户重新输入
        if (!redisUtils.hget(Constant.EMAIL_PREFIX,user.getUsername()).toString().equals(registerDto.getCode())){
            throw new EmailException("邮箱验证码错误，请重新输入！");
        }

        // 密码加密
        user.setPassword(passwordEncoder.encode(registerDto.getPassword()));

        // 删除掉该名用户在redis中的缓存数据
        redisUtils.hdel(Constant.EMAIL_PREFIX,user.getUsername());

        // 保存并且返回成功
        return Result.success(userService.saveOrUpdate(user));
    }
}